﻿using System;

namespace TypeBuilderFx.Core
{
    /// <summary>Base class for <see cref="TypeEmitterBase"/> options.</summary>
    public class TypeEmitterOptions : ICloneable
    {
        private ITypeEmitterCache _cache = TypeEmitterCacheProvider.Default;
        private string _emittedTypeName = string.Empty;
        private string _emittedFactoryTypeName = string.Empty;
        private Type _exposedInterfaceType;

        /// <summary>Default constructor, setting default values.</summary>
        public TypeEmitterOptions()
        {
			IsPrivateImplementation = false;
            IsEmittingThrowNotSupported = false;
        }

        /// <summary>The type emitter cache to use.</summary>
        /// <remarks>
        /// This property doesn't need to be used to compare two <see cref="TypeEmitterOptions"/> since this is useful
        /// inside a given cache (to decide if a factory already exists with those options).
        /// </remarks>
        public ITypeEmitterCache Cache
        {
            get { return _cache; }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                _cache = value;
            }
        }

        /// <summary>Gets or sets the emitted type name.</summary>
        /// <remarks>This property is optional.  If no name are given, the type emitter cache will assign a unique one.</remarks>
        public string EmittedTypeName
        {
            get { return _emittedTypeName; }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                _emittedTypeName = value;
            }
        }

        /// <summary>Gets or sets the emitted factory type name.</summary>
        /// <remarks>This property is optional.  If no name are given, the type emitter cache will assign a unique one.</remarks>
        public string EmittedFactoryTypeName
        {
            get { return _emittedFactoryTypeName; }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                _emittedFactoryTypeName = value;
            }
        }

        /// <summary>The interface type the emitted type is going to expose.</summary>
        public Type ExposedInterfaceType
        {
            get { return _exposedInterfaceType; }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                _exposedInterfaceType = value;
            }
        }

		/// <summary>
		/// Gets or sets the policy for member (method / property / event) implementation:
		/// <c>true</c> means <see cref="ExposedInterfaceType"/> is implemented privately while
		/// <c>false</c> means it's implemented publically.
		/// </summary>
		/// <remarks>Default is <c>false</c>.</remarks>
		public bool IsPrivateImplementation {get;set;}

        /// <summary>
        /// Switch indicating if the type emitter should emit a throw of <see cref="NotSupportedException"/>
        /// when delegates can't implement a member (as oppose to throw while emitting).
        /// </summary>
        /// <remarks>Default is <c>false</c>.</remarks>
        public bool IsEmittingThrowNotSupported { get; set; }

        #region ICloneable Members
        /// <summary>Implements <see cref="ICloneable"/>.</summary>
        /// <remarks>
        /// This implements a memberwise clone, using the default <see cref="object"/> implementation.  If this isn't
        /// enough, this method should be overriden.
        /// </remarks>
        /// <returns></returns>
        public virtual object Clone()
        {
            return MemberwiseClone();
        }
        #endregion

        #region object Methods
        /// <summary>Must be overriden by derived classes in order to be able to use as a key in an dictionary.</summary>
        /// <remarks>Isn't expected not to throw if <see cref="Validate"/> throws.</remarks>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return _emittedTypeName.GetHashCode()
                ^ _emittedFactoryTypeName.GetHashCode()
                ^ ExposedInterfaceType.GetHashCode()
				^ IsPrivateImplementation.GetHashCode()
                ^ IsEmittingThrowNotSupported.GetHashCode();
        }

        /// <summary>Must be overriden by derived classes in order to be able to use as a key in an dictionary.</summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            TypeEmitterOptions options = obj as TypeEmitterOptions;

            return options != null
                && object.ReferenceEquals(options.Cache, Cache)
                && string.Equals(EmittedTypeName, options.EmittedTypeName)
                && string.Equals(EmittedFactoryTypeName, options.EmittedFactoryTypeName)
                && options.ExposedInterfaceType == ExposedInterfaceType
				&& options.IsPrivateImplementation == IsPrivateImplementation
                && options.IsEmittingThrowNotSupported == IsEmittingThrowNotSupported;
        }
        #endregion

        /// <summary>Validates fields to make sure they are all coherent.</summary>
        /// <remarks>Should throw exceptions if something is invalid, otherwise returns.</remarks>
        public virtual void Validate()
        {
            if (_exposedInterfaceType == null)
            {
                throw new ArgumentNullException("ExposedInterfaceType");
            }
            if (!_exposedInterfaceType.IsInterface)
            {
                throw new ArgumentException("Isn't an interface type", "ExposedInterfaceType");
            }
        }
    }
}